﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Net;
using System.Threading;
using IEx.Common;
using IEx.Utilities;

namespace IEx
{
    public class RequestProcess
    {

        private RequestQueue requestQueue;
        private DownloadQueue downloadQueue;
        private const int LIMITTED_THREAD = 8;
        //#if !DEBUG
        //        private const int LIMITTED_THREAD = 8;
        //#else
        //        private const int LIMITTED_THREAD = 1; // Use 1 thread to debug easily
        //#endif

        private List<SourceItem> pickedQueues;
        private WebRequestRetryPolicy retry;
        private Dictionary<int, WaitHandle> waitHandles;
        private static object locked = new object();
        private int totalProcess = 0;
        private int completeProcess = 0;


        public RequestProcess(RequestQueue requestQueue, DownloadQueue downloadQueue)
        {
            this.requestQueue = requestQueue;
            this.downloadQueue = downloadQueue;
            this.pickedQueues = new List<SourceItem>();
            retry = new WebRequestRetryPolicy();
            waitHandles = new Dictionary<int, WaitHandle>();
        }

        /// <summary>
        /// Fired after all items in queue have proceeded successful
        /// </summary>
        public event FinishEventHandler OnFinish;

        /// <summary>
        /// Start picking items in queue to create requests
        /// </summary>
        public void Start(bool isNewScanningRound = true)
        {
            if (isNewScanningRound)
            {
                completeProcess = 0;
                totalProcess = 0;
            }

            Logger.WriteLogInfo("*** START SCANNING SEARCH PAGES ***");
            int index = 0;
            while (true)
            {
                if (requestQueue.QueueCount > 0)
                {
                    totalProcess++;

                    SourceItem queue = requestQueue.Dequeue();
                    this.AddThread(index, new ManualResetEvent(false));
                    ThreadPool.QueueUserWorkItem((o) =>
                    {
                        ThreadArgs arg = o as ThreadArgs;
                        if (arg != null)
                        {
                            if (this.Request(arg.Item))
                            {
                                completeProcess++;
                            }
                            WaitHandle await = this.GetWaitHandle(arg.Index);
                            if (await != null)
                            {
                                ((ManualResetEvent)await).Set();
                                this.RemoveThread(arg.Index);
                            }

                        }
                    }, new ThreadArgs(index, queue));

                    index++;
                }

                if (this.GetThreadCount() == LIMITTED_THREAD)
                {
                    // Wait while pool is full   
                    while (true)
                    {
                        Thread.Sleep(100);
                        if (this.GetThreadCount() < LIMITTED_THREAD)
                            break;
                    }
                }

                // Wait for the all threads finish
                if (this.GetThreadCount() == 0 && requestQueue.QueueCount == 0)
                {
                    break;
                }
            }

            Thread.Sleep(1000);
            if (requestQueue.QueueCount != 0)
            {
                this.Start(false);
                return;
            }

            if (this.OnFinish == null)
                Logger.WriteLogInfo("OnFinish event handler is null");

            if (this.OnFinish != null)
                this.OnFinish.Invoke(new FinishEventArg(totalProcess, completeProcess));
        }

        /// <summary>
        /// Get response from request url
        /// </summary>
        /// <param name="RequestUrl"></param>
        /// <returns></returns>
        public string GetWebResponse(string RequestUrl, string company)
        {
            try
            {
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(RequestUrl);
                request.UserAgent = "Foo";
                request.Accept = "*/*";

                //Set timeout for request
                int requestTimeout = 100000;
                int.TryParse(ConfigurationManager.AppSettings["MaxRequestTimeout"], out requestTimeout);
                request.Timeout = requestTimeout;

                // get response html
                return retry.ExecuteAction<string>(() =>
                {
                    return (new WebResponseEncoding(request)).GetResponse();
                });
            }
            catch (Exception ex)
            {
                // write log
                string message = string.Format(@"
    Company: {0}
    Request fail: {1}
    Search key: {2}", company, RequestUrl, string.Empty);
                Logger.WriteError(message, ex);

                return string.Empty;
            }
        }

        public string GetWebResponse(SourceItem item)
        {
            return GetWebResponse(item.Url, item.SourceCode);
        }

        private int GetThreadCount()
        {
            lock (locked)
            {
                return this.waitHandles.Count;
            }
        }

        private void RemoveThread(int index)
        {
            lock (locked)
            {
                if (this.waitHandles.ContainsKey(index))
                    this.waitHandles.Remove(index);
            }
        }

        private void AddThread(int index, WaitHandle waitHandle)
        {
            lock (locked)
            {
                if (!this.waitHandles.ContainsKey(index))
                    this.waitHandles.Add(index, waitHandle);
            }
        }

        private WaitHandle GetWaitHandle(int index)
        {
            lock (locked)
            {
                if (this.waitHandles.ContainsKey(index))
                    return this.waitHandles[index];
                return null;
            }
        }

        private WaitHandle[] GetAllWaitHandles()
        {
            lock (locked)
            {
                WaitHandle[] waitHandles = new WaitHandle[this.GetThreadCount()];
                this.waitHandles.Values.CopyTo(waitHandles, 0);
                return waitHandles;
            }
        }

        /// <summary>
        /// Begin to send a request to get stream of response
        /// </summary>
        /// <param name="item"></param>
        private bool Request(SourceItem item)
        { 
            //int milliseconds = 1000;
            //Thread.Sleep(milliseconds);//Set time out after 2 seconds to request next
            try
            {
                if (item.RequestBuilder != null)
                {
                    item.RequestBuilder.BeginBuildRequest(item);
                }

                Logger.WriteLogInfo("Start scanning Url: " + item.Url);
                Logger.Debug("Scanning Url: " + item.Url);

                HttpWebRequest request = this.CreateRequest(item.Url);
                request.UserAgent = "Foo";
                request.Accept = "*/*";
                //Set timeout for request
                int requestTimeout = 100000;
                int.TryParse(ConfigurationManager.AppSettings["MaxRequestTimeout"], out requestTimeout);
                request.Timeout = requestTimeout;

                if (item.RequestBuilder != null)
                {
                    item.RequestBuilder.BuildRequest(ref request);
                }

                if (item.HtmlParser != null)
                {
                    string html = "";

                    // get response html
                    retry.ExecuteAction(() =>
                    {
                        html = (new WebResponseEncoding(request)).GetResponse();
                    });

                    // detect announcements from html
                    if (!string.IsNullOrEmpty(html))
                    {
                        Logger.WriteLogInfo("Done scanning Url: " + item.Url);

                        item.HtmlParser.Parse(
                            item,
                            html,
                            (parserItem) => downloadQueue.Enqueue(parserItem),
                            (sourceItem) => requestQueue.Enqueue(sourceItem)
                        );
                    }
                    else
                    {
                        if (item.SourceId == 7)
                        {
                            Logger.WriteLogInfo("Rerutns empty on scanning Url: " + item.Url);
                        }
                    }
                }

                return true;
            }
            catch (Exception ex)
            {
                // insert fail request into XML file
                if (item.FailId == 0)
                {
                    item.FailId = DateTime.Now.Ticks;
                    Utility.SaveFailRequests(item);
                }

                // write log
                string message = string.Format(@"
    Company: {0}
    Request fail: {1}
    Search key: {2}", item.SourceCode, item.Url, item.SearchCode);
                Logger.WriteError(message, ex);
                return false;
            }
        }

        /// <summary>
        /// Create an instance of <see cref="System.Net.WebRequest"/>
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        private HttpWebRequest CreateRequest(string url)
        {
            return (HttpWebRequest)WebRequest.Create(url);
        }

        class ThreadArgs
        {
            public ThreadArgs(int index, SourceItem item)
            {
                this.Index = index;
                this.Item = item;
            }
            public int Index
            {
                get;
                set;
            }
            public SourceItem Item
            {
                get;
                set;
            }
        }
    }
}
